#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Threading;
using UTable.Logger;

namespace UTable.Objects.Handlers.Gestures
{
    public class OneContactGestureRecognizer: GestureRecognizer
    {
        #region Private Fields

        Point _originalFingerPosition;

        Point _originalDragPosition;

        int _currentContactId;

        long _FingerDownTimeTick;

        Point _previousFingerPosition;

        DispatcherTimer _holdTimer;

        private Range _contactNumberRange;

        PhysicsTracker _physicsTracker = new PhysicsTracker();

        private GestureInfo _holdGestureInfo = null;

        private bool _isHoldGestureRaised = false;

        #endregion

        #region Private Methods

        void _holdTimer_Tick(object sender, EventArgs e)
        {
            if (_holdTimer != null)
            {
                _holdTimer.Stop();
                _holdTimer = null;

                if (GestureDetected != null && this.EnabledGestures.Contains(OneContactGestureType.Hold) && !_isHoldGestureRaised)
                {
                    GestureDetected(this, new GestureDetectedEventArgs(new HoldGesture(_currentContactId, _previousFingerPosition, _holdGestureInfo), 1));
                    _isHoldGestureRaised = true;
                }
            }
        }

        #endregion

        #region Properties

        public double MaxTapDistance { get; set; }

        public TimeSpan MaxTapTime { get; set; }

        public OneContactStateType State { get; set; }

        public bool TrackVelocity
        {
            get { return _physicsTracker.TrackVelocity; }
            set { _physicsTracker.TrackVelocity = value; }
        }

        public double MinVelocityIntervalTime
        {
            get { return _physicsTracker.MinVelocityIntervalTime; }
            set { _physicsTracker.MinVelocityIntervalTime = value; }
        }

        public TimeSpan MinHoldTime { get; set; }

        public List<OneContactGestureType> EnabledGestures { get; set; }

        #endregion

        public OneContactGestureRecognizer()
        {
            _contactNumberRange = new Range(1, 1);
            State = OneContactStateType.Hold;

            // initialize the variables
            EnabledGestures = new List<OneContactGestureType>();
            MaxTapDistance = 20;
            TrackVelocity = false;
            MinHoldTime = TimeSpan.FromMilliseconds(2000);
            MaxTapTime = TimeSpan.FromHours(100);
        }

        #region GestureRecognizer Members

        public Range ContactNumberRange
        {
            get { return _contactNumberRange; }
        }

        public int Priority
        {
            get { return 10; }
        }

        public bool IsSupportedGesture(Type gesture)
        {
            return gesture.IsSubclassOf(typeof(OneContactGesture));
        }

        public void StartRecognize(GestureInfo info)
        {
            Point p = (GestureEngine.TargetNode as UIElement).TranslatePoint(info.Contacts[0].CurrentPosition, GestureEngine.RelativeElement);
            _currentContactId = info.Contacts[0].ID;
            _originalFingerPosition = _previousFingerPosition = p;
            _FingerDownTimeTick = info.Contacts[0].CurrentTimestamp;
            _holdGestureInfo = info;
            _isHoldGestureRaised = false;
            State = OneContactStateType.Hold;

            // start the hold timer
            if (this.EnabledGestures.Contains(OneContactGestureType.Hold))
            {
                _holdTimer = new DispatcherTimer();
                _holdTimer.Tick += new EventHandler(_holdTimer_Tick);
                _holdTimer.Interval = MinHoldTime;
                _holdTimer.Start();
            }
        }

        public void Recognizing(GestureInfo info)
        {
            Point p = (GestureEngine.TargetNode as UIElement).TranslatePoint(info.Contacts[0].CurrentPosition, GestureEngine.RelativeElement);
            if (State == OneContactStateType.Hold)
            {
                if ((p - this._originalFingerPosition).Length > MaxTapDistance)
                {
                    this.State = OneContactStateType.Drag;
                    _originalDragPosition = _previousFingerPosition;
                    if (GestureDetected != null && this.EnabledGestures.Contains(OneContactGestureType.Translate) && !_isHoldGestureRaised)
                        GestureDetected(this, new GestureDetectedEventArgs(new TranslateGesture(_currentContactId, p, info, TranslateGesture.TranslateType.Start, new Vector(),_physicsTracker.UpdateVelocity(p, info.Contacts[0].CurrentTimestamp)), 1));

                    // stop holding if needed
                    if (_holdTimer != null)
                    {
                        _holdTimer.Stop();
                        _holdTimer = null;
                    }
                }
            }
            else if (State == OneContactStateType.Drag)
            {
                if (GestureDetected != null && this.EnabledGestures.Contains(OneContactGestureType.Translate) && !_isHoldGestureRaised)
                {
                    GestureDetected(this, new GestureDetectedEventArgs(new TranslateGesture(_currentContactId, p, info, TranslateGesture.TranslateType.Move, p - _previousFingerPosition, _physicsTracker.UpdateVelocity(p, info.Contacts[0].CurrentTimestamp)), 1));
                }
            }
            _previousFingerPosition = p;
        }

        public void EndRecognize(GestureInfo info)
        {
            Point p = (GestureEngine.TargetNode as UIElement).TranslatePoint(info.Contacts[0].CurrentPosition, GestureEngine.RelativeElement);

            // stop holding if needed
            if (_holdTimer != null)
            {
                _holdTimer.Stop();
                _holdTimer = null;
            }

            // raise click event if needed
            if (EnabledGestures.Contains(OneContactGestureType.Click))
            {
                if (GestureEngine.TargetNode is FrameworkElement)
                {
                    FrameworkElement element = GestureEngine.TargetNode as FrameworkElement;
                    Point cp = info.Contacts[0].CurrentPosition;
                    if (cp.X >= 0 && cp.Y >= 0 && cp.X <= element.ActualWidth && cp.Y <= element.ActualHeight)
                    {
                        if (GestureDetected != null && !_isHoldGestureRaised)
                            GestureDetected(this, new GestureDetectedEventArgs(new ClickGesture(_currentContactId, p, info), 1));
                    }
                }
            }

            if (State == OneContactStateType.Hold)
            {
                // raise tap event
                if (GestureDetected != null && this.EnabledGestures.Contains(OneContactGestureType.Tap) && !_isHoldGestureRaised
                    && info.Contacts[0].CurrentTimestamp - _FingerDownTimeTick < MaxTapTime.TotalMilliseconds
                    && info.StartType == GestureChangeType.DirectInput && info.EndType == GestureChangeType.ContactRemoved)
                    GestureDetected(this, new GestureDetectedEventArgs(new TapGesture(_currentContactId, p, info), 1));
            }
            else if (State == OneContactStateType.Drag)
            {
                // release the finger capture
                // Node.UncaptureFinger(fArgs.FingerID);

                // release drag event
                if (GestureDetected != null && this.EnabledGestures.Contains(OneContactGestureType.Translate) && !_isHoldGestureRaised)
                {
                    GestureDetected(this, new GestureDetectedEventArgs(new TranslateGesture(_currentContactId, p, info, TranslateGesture.TranslateType.End, new Vector(), _physicsTracker.UpdateVelocity(p, info.Contacts[0].CurrentTimestamp)), 1));
                }
            }
        }

        public LocalGestureEngine GestureEngine { get; set; }

        public event GestureDetectedEventHandler GestureDetected;

        #endregion
    }

    public enum OneContactGestureType
    {
        /// <summary>
        /// Raised when one finger down, and up without 
        /// moving exceeding <see cref="UTable.Objects.Handlers.SingleFingerGestureHandler.MaxTapDistance"/>
        /// </summary>
        Tap,
        /// <summary>
        /// Raised when one finger down, holding for <see cref="UTable.Objects.Handlers.SingleFingerGestureHandler.MinHoldTime"/> without 
        /// moving exceeding <see cref="UTable.Objects.Handlers.SingleFingerGestureHandler.MaxTapDistance"/>
        /// </summary>
        Hold,
        /// <summary>
        /// Raised when one finger down, and up within the bound 
        /// of <see cref="UTable.Objects.Handlers.SingleFingerHandler.Node"/>
        /// </summary>
        Click,
        /// <summary>
        /// Raised when one finger down, and move exceeding <see cref="UTable.Objects.Handlers.SingleFingerGestureHandler.MaxTapDistance"/>
        /// </summary>
        Translate,
    }

    public enum OneContactStateType
    {
        Hold,
        Drag,
    }

    public class OneContactGesture: Gesture
    {
        protected Point _position;
        protected int _contactID;

        public OneContactGesture(int contactId, Point position, GestureInfo info)
        {
            _contactID = contactId;
            _position = position;
            Info = info;
        }

        public Point Position { get { return _position; } }

        public int ContactID { get { return _contactID; } }
    }

    public class TapGesture: OneContactGesture
    {
        public TapGesture(int contactId, Point position, GestureInfo info)
            : base(contactId, position, info)
        {
        }

        public override string ToString()
        {
            return "Tap " + ContactID + ": " + Position;
        }
    }

    public class TranslateGesture: OneContactGesture
    {
        private TranslateType _type;

        public Vector Velocity { get; protected set; }

        public Vector Offset { get; private set; }

        public enum TranslateType
        {
            Start,
            Move,
            End
        }

        public TranslateType Type { get { return _type; } }

        public TranslateGesture(int contactId, Point position, GestureInfo info, TranslateType type, Vector offset, Vector velocity)
            : base(contactId, position, info)
        {
            _type = type;
            Offset = offset;
            Velocity = velocity;
        }

        public override string ToString()
        {
            return "Translate " + Type + ": " + Offset;
        }
    }

    public class HoldGesture: OneContactGesture
    {
        public HoldGesture(int contactId, Point position, GestureInfo info)
            : base(contactId, position, info)
        {
        }
        
    }

    public class ClickGesture : OneContactGesture
    {
        public ClickGesture(int contactId, Point position, GestureInfo info)
            : base(contactId, position, info)
        {
        }
    }
}
